home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Windows Expert
/
Windows Expert.iso
/
program
/
wintech1.zip
/
DPMI.ZIP
/
DPMI.TXT
next >
Wrap
Text File
|
1991-12-23
|
14KB
|
267 lines
The code presented here is implemented in Turbo Pascal For Windows. The
concepts illustrated apply to any Windows development environment. This code
is adapted from code I wrote for TurboPower Software's NETBIOS unit included
with B-Tree Filer network version. It is reproduced here with TurboPower's
permission.
The code is provided in five source files. They are each described below:
WINDPMI.PAS
This is a unit that implements the the core DPMI routines. This unit is a
trimed down version of the unit by the same name distributed by TurboPower
Software.
TNETBIOS.PAS
This unit defines the various NetBIOS constants and types used by the other
source files.
WNETBIOS
This file implements the NetBIOS calls in a DLL.
UNETBIOS
This unit defines the interface for the routine in WNETBIOS.DLL.
NBCHAT
A simple NetBIOS chat program that runs under Windows. NBCHAT is a primitive
Windows application in that it uses Borland's WinCrt unit. WinCrt is a unit
that makes normal Turbo Pascal WriteLn and ReadLn statements work within a
Windows application. Using WinCrt greatly reduces the size of the NBCHAT
source, an important consideration in a magazine article!
The WinDPMI.PAS file contains the types and routines needed to access DPMI
services. It also interfaces a useful routine called InRealMode. All of the
code presented in this article assumes Windows is operating in protected mode,
so if InRealMode returns true our demo program simply displays a message and
halts.
The source to the WinDPMI unit is written using Turbo Pascal's Inline
Assembler. The code looks deceivingly simple. Making the DPMI calls is the easy
part, figuring out what to do with them is where the challenge lies!
The formal DPMI Specification is available free of charge from Intel. It
explains each of the DPMI services provided here in detail. If you plan on
doing any serious work using DPMI, I'd highly recommend getting the API
specification from Intel.
The next source file, TNETBIOS.PAS, is straitforward. It defines the NetBIOS
Network Control Block structure. Probably the most interesting record is the
WindowsPostType. When writing this code, the part that gave me the most trouble
was attempting to figure out how to deal with NetBIOS Post Routines. The
WindowsPostType is a key component of my solution (more on this later).
WNETBIOS.PAS implements the meat of the routines. It is here that DPMI services
are used to access the NetBIOS API. The first two routines in the source are
routines that act as a shell around the Windows GlobalDosAlloc and
GlobalDosFree routines. The TNETBIOS unit defines a WinNCB as follows:
WinNCB =
record
NR, NP : NCBPtr;
end;
WinNCB is a record holding two pointers to NCBs. NR is a pointer is real mode
format (segment:offset), and NP is a pointer in protected mode format
(selector:offset). Keep in mind, these two pointers both point to the same
block of memory. AllocateWinNCB allocates the real mode memory and sets up
these two pointers, and FreeWinNCB returns the real mode memory to Windows.
Next come two low level routines for making the NetBIOS calls, ClearNCB and
NetBIOSRequest. NetBIOSRequest makes the call to the NetBIOS API by
initializing the important fields of a DPMIRegisters variable, then calling the
DPMI Simulate Real Mode Interrupt service. Note that the DPMIRegisters variable
is filled with zeros before the other fields are initialized. This is
important, since invalid or uninitialized values in segment registers or SS:SP
will cause problems for DPMI.
The NetBIOSRequest procedure takes a single parameter of type NCBPtr. It is the
real mode pointer that is passed to this routine. Care is taken in the body of
NetBIOSRequest to ensure that Turbo Pascal doesn't generate code to load that
pointer into a register pair. Why, you ask? Because if Turbo Pascal generated
its usual code:
LES DI, SomePtr
the CPU would generate an exception error since you would be attempting to load
an invalid selector into the ES segment register! Whenever a segment register
is loaded in protected mode, the CPU will check to see if it is a valid
selector, and if not, a protection fault is triggered. The typecasts are used
to cause Turbo Pascal to treat the segment and offset components of the pointer
as words, not pointers, alleviating the concern that an LES DI statement may be
used.
The next 5 routines make various NetBIOS API calls. They construct an NCB
(accessing the NP protected mode pointer of the WinNCB type), then call
NetBIOSRequest (passing the NR real mode pointer of the WinNCB type). Notice
how the NCB field PostRoutine is initialized in SendDatagram and
ReceiveDatagram. It is passed the field Callback from the WindowsPostType. This
is the value returned by the DPMI service Allocate Real Mode Callback. More on
this when we reach this file's most intriquing routine, CallbackShell.
The routine CancelRequest, NetBIOSAddName, and NetBIOSDeleteName all allocate,
use, and dispose of their own internal WinNCB variables. They can do this
because the routines in question don't return until NetBIOS has completed the
request. Therefore there is no need for the caller to assure the NCBs remain
static, so to simplify things the NCBs are dealt with internally. Note that
CancelRequest also takes a WinNCB as a parameter. That parameter is the WinNCB
for the NetBIOS command to be cancelled. The CancelRequest routine still needs
its own internal WinNCB to issue the cancel command itself.
The next three routines deal with determining NetBIOS' presence. We resort to
using another DPMI routine to determine the current value of the real mode
interrupt 5Ch vector. If the vector is NIL or pointing to the BIOS, then we
know NetBIOS isn't installed. Otherwise, we issue an invalid NetBIOS command
and see if the interrupt 5Ch handler installed returns the NetBIOS invalid
command error. If so, NetBIOS is installed, if not, something else is
processing interrupt 5Ch and NetBIOS is not present. It is interesting that IBM
forgot to include a NetBIOS Present function call when they designed NetBIOS!
Now comes the real hairy part. It is no trivial matter to deal with NetBIOS
Post Routines! The last three routines in WNETBIOS deal with this tricky issue.
Before we get to the code, lets examine the data structures involved more
closely:
RealModeCallbackProc = Pointer;
NetBiosPostRoutine = procedure(LastError : Byte; N : WinNCBPtr);
WindowsPostType =
record
Regs : DPMIRegisters;
Post : NetBiosPostRoutine;
CallBack : RealModeCallbackProc;
DataSegm : Word;
NCBs : WinNCBPtr;
end;
The DPMI AllocateRealModeCallbackAddr procedure requires a pointer to a
protected mode procedure to call, a variable of type DPMIRegisters to pass data
in, and a pointer to return the real mode callback address. In our
WindowsPostType, Regs is the DPMIRegisters, and Callback is the real mode
callback address returned by DPMI. Post is a routine of type NetBIOSPostRoutine
which will ultimately be called, and NCBs is a pointer to the WinNCB associated
with this post routine. The DataSegm field is the data segment of the program
using the callback. The WindowsPostType contains all the data necessary to pull
off the tough task of allowing our protected mode application to take advantage
of NetBIOS Post Routines.
WNETBIOS defines a routine called GetWindowsPostRoutine to fill in the
WindowsPostType variable. GetWindowsPostRoutine is passed the
NetBIOSPostRoutine to be called, the data segment of the caller, and a variable
of type WindowsPostType. This routine calls the DPMI Allocate Real Mode
Callback Address service to obtain a pointer to a procedure that can be called
in real mode. This real mode callback will in turn call a specified protected
mode procedure. If you look at the source to GetWindowsPostRoutine, you'll see
the protected mode procedure we ask the real mode callback to call is not the
NetBIOSPostRoutine, but rather a routine called CallbackShell.
CallbackShell is where we separate the men from the boys. This is a routine
that knows the context of the system at the time the real mode callback calls
the protected mode routine. CallbackShell then gets the information it needs to
safely call the NetBIOSPostRoutine that had been specified in the call to
GetWindowsPostRoutine.
DPMI doesn't go out of its way to make this easy for you. When the protected
mode routine is called by the callback, you must grab the return address off of
the real mode stack, place it into the drCS and drIP fields of the
DPMIRegisters variable, and issue an IRET instruction. On entry into the
protected mode routine, DPMI's real mode callback places a pointer to the real
mode stack in DS:SI (DS contains a selector), and a pointer to the
DPMIRegisters type in ES:DI. Our CallbackShell routine has the task of calling
the NetBIOSPostRoutine. CallbackShell takes the following action:
1) save all the registers we'll be modifying
2) grab the return address off the real mode stack
3) put the return address in the drCS:drIP fields of the DPMIRegisters
4) push the NetBIOS return code
5) push a pointer to the WinNCB associated with this event
6) setup the client program's DATA segment
7) call the user specified NetBIOSPostRoutine
8) restore the registers
9) issue an IRET
The need for callback routines is not limited to NetBIOS. Similar steps must be
taken for Event Service Routines under NetWare's IPX and SPX protocols.
The UNETBIOS.PAS unit defines the interface to the WNETBIOS DLL.
The final source file is the NBCHAT.PAS program. This is a quick and dirty chat
program designed to test the routines presented here. The NBCHAT program first
confirms that Windows is operating in Standard or 386 Enhanced mode. It then
checks to see if NetBIOS is loaded. If so, it prompts the user for a NetBIOS
name to give this workstation, and a name for the chat partner. It then adds
the NetBIOS name for this workstation, allocates necessary resources, and
enters the message loop.
The program sets up an ExitProc to handle program termination. Global typed
constants (in Turbo Pascal, typed constants are simply initialized variables)
are defined to track the state of various variables. The ExitProc uses these
booleans to safely shut down any pending NetBIOS events and to release all
allocated resources.
The main program logic is implemented in two routines: MessageLoop and
PostRoutine. Here's how MessageLoop is implemented:
procedure MessageLoop;
var
C : Char;
begin
WriteLn('Press space bar to enter message, ESC to quit');
ReceiveDatagram(RecN, NBNameNo, False, Post, SizeOf(String), SR);
repeat
while not KeyPressed do begin
if Pending then
ShowIncoming;
end;
C := ReadKey;
if C <> ^[ then
SendOutgoing;
until C = ^[;
end;
MessageLoop posts the first ReceiveDatagram call. It then sits in a loop until
the escape key is pressed. If a key other than escape is pressed, then the user
is prompted for a message to send, and the message is sent to the chat partner.
While not processing messages to send, the loop constantly checks the status of
the global boolean Pending. When Pending becomes True, it means an incoming
message is waiting to be processed.
When the first ReceiveDatagram call is made by MessageLoop, it associates the
call with a NetBIOS Post Routine appropriately called PostRoutine. When a
datagram is received, NetBIOS (and our DPMI callback logic) will automatically
call the Post Routine. Our Post Routine looks like this:
procedure PostRoutine(LastError : Byte; N : WinNCBPtr); Far;
begin
if Exiting then Exit;
Pending := True;
if LastError = 0 then
Msg := SP^
else
Msg := 'NetBIOS error = ' + Num2Str(LastError);
ReceiveDatagram(N^, NBNameNo, False, Post, SizeOf(String), SR);
end;
It first checks to see if the program is shutting down in the ExitProc. If so,
it exits without doing anything else. This prevents the PostRoutine from
reissuing the ReceiveDatagram call. If the program isn't exiting, the global
boolean Pending is set to True. If NetBIOS encountered an error, the global
variable Msg is set to an error message, otherwise it is given the incoming
string. Then the ReceiveDatagram call is reissued so NetBIOS can receive the
next incoming datagram. Note that a Post Routine executes in an interrupts off
state, much as if it was a interrupt service routine. You must be very careful
what you do in post routines. This post routine simply sets some variables
indicating a message has arrived and resubmits the WinNCB to listen for the
next datagram. You cannot call DOS services from a Post Routine, but you may
call most NetBIOS API calls.
The global variables SR and SP are pointers to strings. SP is a protected mode
pointer, and SR is a real mode pointer. They are allocated through a call to
GlobalDosAlloc. When accessing the string in our application, we must use SP.
However, when we pass the string to NetBIOS, we must use SR, the real pointer.
This is the same issue as discussed earlier when talking about the WinNCB
record.
This is an overly simplistic program. In the real world, a program would
probably post multiple ReceiveDatagram calls using a pool of WinNCBs to make
sure rapidly incoming events were processed properly. The goal here wasn't to
write the ultimate NetBIOS chat program, but to show how to use real mode API
and callback services within a protected mode application.